#include "GLShaderManager.hh"

#include <fstream>
#include <sstream>
#include <iostream>

namespace
{
   std::string readASCIIFile(const char* file)
   {
      std::ifstream ifs(file);
      if(ifs.is_open()) {
	 std::stringstream ss;
	 ss<<ifs.rdbuf();
	 return ss.str();
      }

      return "";
   }

}

GLShaderManager* GLShaderManager::_mSingleton = NULL;

GLShaderManager::GLShaderManager(void)
   :_mProgramID(0), _mLastAttribPos(0)
{
   
}

GLShaderManager* GLShaderManager::GetSingleton(void)
{
   if(!_mSingleton) {
      _mSingleton = new GLShaderManager();
   }
   
   return _mSingleton;
}

bool GLShaderManager::CompileShader(const char* shader_file, GLenum shader_type)
{
   GLuint shID = glCreateShader(shader_type);
   std::string shSrc = readASCIIFile(shader_file);
   std::string error("");

   if(shSrc.compare("")) {
      const char* cstrSource = shSrc.c_str();
      glShaderSource(shID, 1, &cstrSource, NULL);
      glCompileShader(shID);
      if(!CheckShaderCompile(shID, error )) {
	 return false;
      }
   }
   else {
      error = "Empty Shader Source";
      return false;
   }

   if(error.compare("")) {
      std::cout<<"Compile Shader Error:"<<error.c_str()<<std::endl;
      return false;
   }

   GLShaderInfo shader(shID, shader_type, shSrc);
   _mShaderVector.push_back(shader);
   return true;
}

bool GLShaderManager::LinkToProgram(void)
{
   GLuint program = glCreateProgram();

   std::vector<GLShaderInfo>::const_iterator it = _mShaderVector.begin();
   
   for(; it!=_mShaderVector.end(); it++) {
      glAttachShader(program, (*it).mCompiledID);
   }

   glLinkProgram(program);

   std::string error("");
   if(!CheckShaderLink(program, error)) {
      return false;
   }
   
   if(error.compare(""))
   {
      std::cout<<"Link Shader Error:"<<error.c_str()<<std::endl;
      return false;
   }
   _mProgramID = program;
   return true;
}

void GLShaderManager::Run(void)
{
   glUseProgram(_mProgramID);
}

bool GLShaderManager::FillGLInstanceAttribBegin(unsigned int buf_id)
{
   if(glIsBuffer(buf_id)) {
      glBindBuffer(GL_ARRAY_BUFFER, buf_id);
      return true;
   }
   return false;
}

bool GLShaderManager::FillGLInstanceAttrib(const char* attrib_name, InstanceAttribType type, unsigned int attrib_stride)
{
   GLint pos[4];
   int npos = 0;
   int components=0;
   
   switch(type)
   {
   case InstAttr_Matrix:
      pos[0] = glGetAttribLocation(_mProgramID, attrib_name);
      pos[1] = pos[0] +1;
      pos[2] = pos[0] +2;
      pos[3] = pos[0] +3;
      npos = 4;
      components = 16;
      break;

   case InstAttr_Vector:
      pos[0] = glGetAttribLocation(_mProgramID, attrib_name);
      npos = 1;
      components = 4;
      break;

   case InstAttr_Float:
      pos[0] = glGetAttribLocation(_mProgramID, attrib_name);
      npos = 1;
      components = 1;
      break;

   default:
      return false;
   }

   for(int i=0; i<npos; i++)
   {
      glEnableVertexAttribArray(pos[i]);
      glVertexAttribPointer(pos[i], components/npos, GL_FLOAT, GL_FALSE, attrib_stride, (void*)(sizeof(float)* _mLastAttribPos ));
      _mLastAttribPos += components/npos;
      glVertexAttribDivisor(pos[i], 1);
   }

   return true;
}

void GLShaderManager::FillGLInstanceAttribEnd(void)
{
   glBindBuffer(GL_ARRAY_BUFFER, 0);
   _mLastAttribPos = 0;
}


bool GLShaderManager::CheckShaderCompile(unsigned int shader_id, std::string& error)
{
   GLint sta;
   glGetShaderiv(shader_id, GL_COMPILE_STATUS, &sta);
   if(GL_FALSE == sta)
      {
	 GLint maxlength=0;
	 GLint infolength=0;
	 glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &maxlength);
	 char log[maxlength];
	 glGetShaderInfoLog(shader_id, maxlength, &infolength, log);
	 error = log;
	 return false;
      }
   return true;
}

bool GLShaderManager::CheckShaderLink(unsigned int program_id, std::string& error)
{
   GLint sta;
   glGetShaderiv(program_id, GL_LINK_STATUS, &sta);
   if(GL_FALSE == sta)
      {
	 GLint maxlength=0;
	 GLint infolength=0;
	 glGetShaderiv(program_id, GL_INFO_LOG_LENGTH, &maxlength);
	 char log[maxlength];
	 glGetShaderInfoLog(program_id, maxlength, &infolength, log);
	 error = log;
	 return false;
      }
   return true;
}
